package edu.gatech.cs3240;

import java.util.ArrayList;

/**
 * The GrammarChecker class reads in and evaluates the expression passed from
 * a file.
 * @author DPrince
 */
public class GrammarChecker {
	/**
	 * A nested representing the stack used by the GrammarChecker
	 * @throws StackEmptyException
	 */
	private class Stack {
		ArrayList<Token> tokens;
		
		/**
		 * Constructor -- sets up the tokens instance variable and prepares for
		 * work
		 */
		public Stack(){	
		  tokens = new ArrayList<Token>();
		}
		
		/**
		 * Pushes a new token onto the stack
		 * @param tok The token to be added
		 */
		public void push(Token tok){
			tokens.add(tok);
		}
		
		/**
		 * Pops the top token off of the stack
		 */
		public void pop(){
		  if(tokens.size() > 0) {
			  tokens.remove(tokens.size() - 1);
		  }
		}
		
		/**
		 * Shows the next token on the stack (but does not pop)
		 * @return The next token on the stack
		 * @throws StackEmptyException Thrown if trying to peek on an empty stack
		 */
		public Token peek() throws StackEmptyException {
		  if(tokens.size() > 0) {
			  return tokens.get(tokens.size() - 1);
		  } else {
		    throw new StackEmptyException();
		  }
		}
		
		
		/**
		 * Is thrown when peek() is called on an empty stack.
		 * @author JMitchell
		 */
		private class StackEmptyException extends Exception {
		  private static final long serialVersionUID = 124L;
		  
		  /**
		   * Constructor -- just calls the superclass
		   */
		  public StackEmptyException() {
		    super();
		  }
		}
		
	}
  
  /** A reference to this GrammarChecker's stack */
	Stack myStack;
	
	/** A reference to this GrammarChecker's grammar */
	Grammar myGrammar;
	
	/**
	 * Parses the passed regular expression (based on the scanner reference
	 * passed in) and generates a parse tree.
	 * @param regEx A reference to the scanner being used for this parse phase
	 * @return The parse tree generated (returns the root node)
	 * @throws UnparseableException Thrown if the regex cannot be parsed
	 */
	public ParseTreeNode parse(Scanner regEx) throws UnparseableException {

		ParseTreeNode root = new ParseTreeNode(myGrammar.getStartToken());
		// TODO: Figure out what to do with the result.  Verify?
		ArrayList<Token> result = expand(myGrammar.getStartToken(), regEx, 
				root, "");
		if(regEx.showCurrent().getValue() != '$') {
			throw new UnparseableException("Parser finished without being " +
					"able to match '" + regEx.showCurrent().getValue() + "'.");
		}
//		if(!result.containsAll(regEx.getTokens()))
//			throw new UnparseableException("Parser made a bad match!");
		
		return root;
	}
	
	/**
	 * Constructor -- initializes the GrammarChecker with a new grammar
	 */
	public GrammarChecker() {
		myGrammar = new Grammar();
	}
	
	/**
	 * A nested exception class thrown when something cannot be parsed
	 */
	public class UnparseableException extends Exception {
	  private static final long serialVersionUID = 13L;
	  
	  /**
	   * Constructor -- generates the exception
	   * @param msg The message to be included when the exception is displayed
	   */
	  public UnparseableException(String msg) {
	    super(msg);
	  }
	}
	
	/**
	 * The meat of the parsing operation.  Using the grammar-defined parse
	 * table, the expand() method substitutes each nonterminal in nt's 
	 * production with the resulting set of terminals and nonterminals
	 * recursively.  The final, returned ArrayList consists entirely of
	 * the produced terminal-only string.  It is meant to validate the regex
	 * input as obedient to the grammar, while simultaneously producing a parse
	 * tree for NFA construction.
	 * @param nt
	 * @param s
	 * @return
	 * @throws UnparseableException
	 */
	public ArrayList<Token> expand(Token.Nonterminal nt, Scanner s, 
			ParseTreeNode node, String debugPreprint) throws UnparseableException {
		// Check the next character from the Scanner to figure out which 
		// production rule to use.
		Token[] prod = nt.getCorrectProduction(s.showCurrent().getValue());
		if(prod == null) {
			throw new UnparseableException("No possible production of '" 
					+ s.showCurrent() + "' from '" + nt + "'.");
		}
		ParseTreeNode child;
		if(prod.length == 0)
			// The nonterminal reduces to nothing (i.e., epsilon).
			node.addChild(Token.Terminal.EPSILON);
//			System.out.println(debugPreprint + "Expanding " + nt + " to EPSILON");
		ArrayList<Token> ret = new ArrayList<Token>();
		for(int i = 0; i < prod.length; i++) {
//			System.out.println(debugPreprint + "Expanding " + nt + ", prod[" + (i+1) + "/" + prod.length + "] = " + prod[i]);
			child = node.addChild(prod[i]);
			if(prod[i] instanceof Token.Terminal) {
				if(((Token.Terminal) prod[i]).getValue() == s.showCurrent().getValue()) {
//					System.out.println(debugPreprint + "consume: " + s.showCurrent().getValue());
					ret.add(s.getNext());
				} else
					throw new UnparseableException("Token mismatch: got '" 
							+ s.showCurrent() + "', expected '" + prod[i] + "'.");
			} else{
//				System.out.println(debugPreprint + ((Token.Nonterminal) prod[i]) + " -> ");
				//System.out.println("Expand: " + prod[i].toString() + ", " + s.showCurrent().toString() + ", " + child.getToken().toString() + ".");
				ret.addAll(expand((Token.Nonterminal) prod[i], s, child, debugPreprint + "\t"));
			}
		}
		return ret;
	}
}
